package com.dizang.sharding.config;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.shardingsphere.api.config.masterslave.MasterSlaveRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.KeyGeneratorConfiguration;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingStrategyConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.MasterSlaveDataSourceFactory;
import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.dizang.sharding.config.algorithm.DbShardingAlgorithm;
import com.dizang.sharding.config.algorithm.TbShardingAlgorithm;
import com.google.common.collect.Lists;

/**
 * 
 * @author kelvin.cai
 *
 */
@Configuration
@MapperScan(basePackages = {"com.dizang.sharding.infrastrusture.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory")
public class ShardingDBConfiguration {

    @Value("${spring.datasource.type}")
    private Class<? extends DataSource> dataSourceType;

    private static final String DS_0_MASTER = "ds0Master";
    private static final String DS_0_SLAVE = "ds0Slave";
    private static final String DS_1_MASTER = "ds1Master";
    private static final String DS_1_SLAVE = "ds1Slave";
    private static final String DS_SHARDING_0 = "dsMasterSlave1";
    private static final String DS_SHARDING_1 = "dsMasterSlave2";

    private static final String USER_SHARDING_DATA_SOURCE = "userSharding";

    @Bean(DS_0_MASTER)
    @ConfigurationProperties(prefix = "spring.shardingsphere.datasource.ds0")
    public DataSource dsUser1(){
        return DataSourceBuilder.create().type(dataSourceType).build();
    }

    @Bean(DS_1_MASTER)
    @ConfigurationProperties(prefix = "spring.shardingsphere.datasource.ds1")
    public DataSource dsUser2(){
        return DataSourceBuilder.create().type(dataSourceType).build();
    }

    @Bean(DS_0_SLAVE)
    @ConfigurationProperties(prefix = "spring.shardingsphere.datasource.slave0")
    public DataSource dsUserSlave1(){
        return DataSourceBuilder.create().type(dataSourceType).build();
    }

    /**
     * user_2
     * @return
     */
    @Bean(DS_1_SLAVE)
    @ConfigurationProperties(prefix = "spring.shardingsphere.datasource.slave1")
    public DataSource dsUserSlave2(){
        return DataSourceBuilder.create().type(dataSourceType).build();
    }

    @Bean(DS_SHARDING_0)
    public DataSource masterSlave1(@Qualifier(DS_0_MASTER) DataSource ds0,@Qualifier(DS_0_SLAVE) DataSource slave0) throws Exception {
        Map<String,DataSource> dataSourceMap = new HashMap<>();
        dataSourceMap.put(DS_0_MASTER, ds0);
        dataSourceMap.put(DS_0_SLAVE, slave0);
        MasterSlaveRuleConfiguration ruleConfiguration = new MasterSlaveRuleConfiguration("dsUser1", DS_0_MASTER, Lists.newArrayList(DS_0_SLAVE));
        return MasterSlaveDataSourceFactory.createDataSource(dataSourceMap, ruleConfiguration, new Properties());
    }

    @Bean(DS_SHARDING_1)
    public DataSource masterSlave2(@Qualifier(DS_1_MASTER) DataSource ds1,@Qualifier(DS_1_SLAVE) DataSource slave1) throws Exception {
        Map<String,DataSource> dataSourceMap = new HashMap<>();
        dataSourceMap.put(DS_1_MASTER, ds1);
        dataSourceMap.put(DS_1_SLAVE, slave1);
        MasterSlaveRuleConfiguration ruleConfiguration = new MasterSlaveRuleConfiguration("dsUser2", DS_1_MASTER, Lists.newArrayList(DS_1_SLAVE));
        return MasterSlaveDataSourceFactory.createDataSource(dataSourceMap, ruleConfiguration, new Properties());
    }

    @Bean(USER_SHARDING_DATA_SOURCE)
    public DataSource ds(@Qualifier(DS_0_MASTER) DataSource ds0, @Qualifier(DS_1_MASTER) DataSource ds1) throws Exception {
        Map<String, DataSource> dataSourceMap = new HashMap<>();
        dataSourceMap.put("db0", ds0);
        dataSourceMap.put("db1", ds1);
        ShardingRuleConfiguration userRule = getUserRule();
        return ShardingDataSourceFactory.createDataSource(dataSourceMap, userRule,  new Properties());
    }

    /**
     * 配置分片规则
     * @return
     */
    private ShardingRuleConfiguration getUserRule(){
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        shardingRuleConfig.getTableRuleConfigs().add(getOrderTableRuleConfiguration());
        shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration("user_id", new DbShardingAlgorithm()));
        shardingRuleConfig.setDefaultTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("user_id",new TbShardingAlgorithm()));
        shardingRuleConfig.getBindingTableGroups().add("t_user");
        shardingRuleConfig.getBroadcastTables().add("t_order");
        return shardingRuleConfig;
    }
    
    private static KeyGeneratorConfiguration getKeyGeneratorConfiguration() {
        KeyGeneratorConfiguration result = new KeyGeneratorConfiguration("SNOWFLAKE", "id");
        return result;
    }
    
    TableRuleConfiguration getOrderTableRuleConfiguration() {
        TableRuleConfiguration result = new TableRuleConfiguration("t_user", "db${0..1}.t_user_${0..2}");
        result.setKeyGeneratorConfig(getKeyGeneratorConfiguration());
        return result;
    }
    

    @Bean("sqlSessionFactory")
    public SqlSessionFactory userSqlSessionFactory(@Qualifier(USER_SHARDING_DATA_SOURCE) DataSource dataSource) throws Exception{
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        return sqlSessionFactoryBean.getObject();
    }

    @Bean("transaction")
    public DataSourceTransactionManager userTransactionManager(@Qualifier(USER_SHARDING_DATA_SOURCE) DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }

}
